/* eslint-disable no-undef */
/*
 * Author: Daniel Stephen, daniel.macc.stephen@gmail.com
 * Date: September, 2016
 * This code was created as part of my masters thesis at Oregon State University.
 * Please contant me before using any code from this application.
 * 
 * The following code is for an interactive flow map depicting US county-to-
 * county migration. It uses a new algorithmic method of arranging flow curves
 * in a manner that adheres to cartographic principles for origin-destination
 * flow maps that improve readability and aesthetics. 
 * 
 * The following Javascript libaries are used in the creation of this map:
 * 
 *    D3.js
 *    TopoJSON.js
 *    jQuery
 *    Bootstrap
 *    simple_statistics.js
 * 
 * The code is written in a similar pattern to the one described here:
 *    http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
 */

/*
 * "Flox" is the primary namespace for the application, and includes several 
 * sub-modules, including:
 *    Flox.Model
 *    Flox.ModelFilter
 *    Flox.MapComponent_d3
 *    Flox.GUI
 *    Flox.GeomUtils
 *    Flox.FlowImporter
 *    Flox.Flow
 *    Flox.FlowLayouter
 *    Flox.ColorUtils
 * 
 * The code is written similar to the pattern described here:
 *     http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
 */
import FlowImporter from "./FlowImporter"
import Flow from "./Flow"
import ModelFilter from "./ModelFilter"
import FlowLayouter from "./FlowLayouter"
import GeomUtils from "./GeomUtils"
import ColorUtils from "./ColorUtils"
import MapComponent_d3 from "./MapComponent_d3"
import Model from "./FloxModel"
import ChordDiagram from "./ChordDiagram";
import MatrixGraphic from "./MatrixGraphic";
import GapminderGraphic from "./GapminderGraphic";
var jQuery = require("jquery");
window.$ = jQuery;


var Flox = (function () {

    var mapComponent,
        model_master,
        startTimeAll,
        endTimeAll,
        numberOfDisplayedFlows,
        currentFilteredModel,

        // This flag becomes and stays false after the starting animation.
        starting = true,

        /**
         * These settings determine which flows are shown on the map when
         *  Flox.updateMap is called. They're changed through the GUI.
         */
        filterSettings = {
            flowType: "net", // can be net or total
            inStateFlows: true, // show county flows inside selected state
            outerStateFlows: true, // show county flows traveling outside selected state
            selectedCounty: false, // A county is selected
            selectedState: false, // A state is selected
            countyMode: false, // County-level flows are being viewed
            stateMode: true, // state-level flows are being viewed 最开始是州模式
            selectedFeatureName: false // the name of the selected feature. False
        },
        my = {}; // public object


    /**
     * Draw the features in the passed-in model to the map.
     */
    function refreshMap(model_copy) {
        if (!model_copy) {
            throw new Error("refreshMap needs a model passed in");
        }
        mapComponent.drawFeatures(model_copy);
    }


    /**
     * Sets up and calls each iteration of FlowLayouter.layoutAllFlows(weight)
     * with the appropriate weight.
     * Decreases the weight of each iteration.
     * Calls moveFlowsIntersectingNodes() during second half of iterations.
     * NOTE: this is run only if webworkers are not enabled or available
     */
    function layoutFlows(model) {

        console.log("Laying out " + model.getFlows().length + " flows...");

        if (model.getFlows().length < 2) {
            console.log("there are fewer than 2 flows, not doing a layout");
            refreshMap(model);
            return;
        }

        var iterations = model.settings.NBR_ITERATIONS,
            initialLocks = model.getLocks(),
            startTime = performance.now(),
            layouter, endTime,
            i, j, weight;

        layouter = new Flox.FlowLayouter(model);

        // Straighten the flows
        layouter.straightenFlows();

        console.log("Running layout iterations, please wait...")

        // Run the first half of iterations, without MFIN
        for (i = 0, j = Math.floor(iterations / 2); i < j; i += 1) {
            //console.log("Layout Iteration " + (i + 1));
            weight = 1 - i / iterations;
            layouter.layoutAllFlows(weight);
        }

        console.log("Half of layout interations complete, keep waiting...")

        // Run second half of iterations, with MFIN
        for (i = Math.floor(iterations / 2); i < iterations; i += 1) {
            //console.log("Layout Iteration " + (i + 1));
            weight = 1 - i / iterations;
            layouter.layoutAllFlows(weight);
            if (model.settings.moveFlowsIntersectingNodes) {
                layouter.moveFlowsIntersectingNodes();
            }
        }

        model.applyLocks(initialLocks);
        endTime = performance.now();
        console.log("Layout time in milliseconds: " + Math.round(endTime - startTime));
    }

    /**
     * 导入州到州的OD数据
     * Import the state-to-state migration data.
     * Make keepSelectedState true if the currently selected state should
     * stay selected.
     */
    function importStateToStateMigrationFlows(keepSelectedState) {

        var //flowPath = "data/census/US_state_migration_2013_flows.csv",
            // flowPath = "data/census/stateToStateFlows.csv",
            flowPath = "FlowData.json",
            stuffImportWorkerNeeds = {};

        // Clear the current model and map.
        // terminateWorkers();
        model_master.deleteAllFlows();
        mapComponent.hideAllCountyBorders();
        mapComponent.removeAllFlows();
        mapComponent.removeNecklaceMap();

        // If the app is just being started, run the startup zoom animation.
        if (starting === false) {
            mapComponent.zoomToFullExtent();
        }

        // filterSettings.stateMode = true;
        // filterSettings.countyMode = false;
        model_master.settings.scaleMultiplier = 4; // FIXME hardcoded
        model_master.settings.datasetName = "states";

        if (!keepSelectedState) {
            my.setSelectedState(false);
        }

        stuffImportWorkerNeeds.flowPath = flowPath;
        stuffImportWorkerNeeds.settings = model_master.settings;


        //绘制流图
        // Import the flows
        // if (window.Worker && model_master.settings.useWebworkers) {//如果支持Worker 直接跳过选择非Worker方式
        //     runImportWorker(stuffImportWorkerNeeds, function (d) {
        //         model_master.deserializeModelJSON(d);
        //         my.updateMap();
        //     });
        // } else {
        console.log("浏览器不支持WebWorker");
        console.log("导入区到区的OD中...");


        Flox.FlowImporter.importStateToStateMigrationFlows(flowPath, function (flows, stateNodes) {
            model_master.initNodes(stateNodes);
            model_master.addFlows(flows);
            my.updateMap();
        });
        // }
    }

    function importTotalCountyFlowData(stateFIPS) {

        var stuffImportWorkerNeeds = {};

        // erase all flows from the model.
        model_master.deleteAllFlows();

        filterSettings.selectedState = stateFIPS;

        // Set the mapScale in the model to the appropriate scale for this map.
        // This scale is used by the layouter!
        // Could it also be used by the renderer?
        // FIXME this is goofy
        model_master.setScaleMultiplierByState(stateFIPS);
        model_master.settings.datasetName = "FIPS" + Number(stateFIPS);

        console.log("Browser cannot use webworkers. UI will be locked during computations.");
        console.log("Importing county flows...");
        Flox.FlowImporter.importTotalCountyFlowData(stateFIPS, function (flows, countyNodes) {
            model_master.initNodes(countyNodes);
            model_master.addFlows(flows);
            console.log(countyNodes);
            console.log(flows);
            my.updateMap();
        });
    }

// PUBLIC =====================================================================

    //引入包来使用

    my.angleDif = function (startToCtrlAngle, fStartToCtrlAngle) {
        return Flox.GeomUtils.angleDif(startToCtrlAngle, fStartToCtrlAngle);
    };

    /**
     * Converts pixel coordinates to latLng,
     * makes a Point object, returns it.
     * lyrPt can be an array [x,y]
     */
    my.pointFromLayerPt = function (lyrPt) {
        var latLng = mapComponent.layerPtToLatLng(lyrPt);
        return new Flox.Point(latLng.lng, latLng.lat);
    };

    /**
     * Convenience constructor for making a Flow when the location of
     * the control point is known.
     * TODO Might not be optimal, since the default constructor
     * calculates a location for the cPt.
     */
    my.createFlowWithCPt = function (sPt, cPt, ePt, val) {
        var newFlow = new Flow(sPt, ePt, val);
        newFlow.setCtrlPt(cPt);
        return newFlow;
    };

    my.refreshMap = function (model_copy) {
        // redraw the flows
        refreshMap(model_copy);
    };

// Returns the model_master, which contains all imported flows. 
    my.getModel = function () {
        return model_master;
    };

    my.getDistanceBetweenPoints = function (p1, p2) {
        var squaredDist = Flox.GeomUtils.squaredDistanceBetweenPoints(p1, p2);
        return Math.sqrt(squaredDist);
    };

    my.latLngToLayerPt = function (latLng) {
        return mapComponent.latLngToLayerPt(latLng);
    };

    my.layerPtToLatLng = function (layerPt) {
        return mapComponent.layerPtToLatLng(layerPt);
    };

    my.layerPtToPoint = function (layerPt) {
        var latLng = mapComponent.layerPtToLatLng(layerPt);
        return new Flox.Point(latLng.lng, latLng.lat);
    };

    my.getDistanceToQuadraticBezierCurve = function (p0x, p0y, p1x, p1y, p2x, p2y, xy) {
        return Flox.GeomUtils.getDistanceToQuadraticBezierCurve(p0x, p0y, p1x, p1y, p2x, p2y, xy);
    };

// Returns [x,y] of the midpoint between two points
    my.midXYBetweenTwoPoints = function (p1, p2) {
        return Flox.GeomUtils.midXYBetweenTwoPoints(p1, p2);
    };

    my.linesIntersect = function (x1, y1, x2, y2, x3, y3, x4, y4) {
        return Flox.GeomUtils.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4);
    };

    my.getLineLineIntersection = function (x1, y1, x2, y2, x3, y3, x4, y4) {
        return Flox.GeomUtils.getLineLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4);
    };


    /**
     * Convert a latLng object into a Point object
     * @param {Object} latLng {lat: latitude, lng: longitude}
     */
    my.latLngToPoint = function (latLng) {
        return new Flox.Point(latLng.lng, latLng.lat);
    };

    /**
     * Project the lat and long of each node to the current map projection,
     * assigning each node
     */
    my.assignXYToNodes = function (nodes) {
        var i, node, xy;
        for (i = 0; i < nodes.length; i += 1) {
            node = nodes[i];
            xy = mapComponent.latLngToLayerPt(node);
            if (xy) {
                node.x = xy.x;
                node.y = xy.y;
            } else {
                console.log("Couldn't project one of the nodes, ID :" + node.id);
            }
        }
    };

// A quick way of making a simple point object.
// Defines latLng AND xy!
    my.Point = function (lat, lng, val, id) {
        this.lat = lat;
        this.lng = lng;
        if (val) {
            this.value = val;
        } else {
            this.value = 1;
        }
        if (id) {
            this.id = id;
        }
        this.incomingFlows = [];
        this.outgoingFlows = [];
    };

    my.rotatePoint = function (pt, origin, angle) {
        return Flox.GeomUtils.rotate(pt, origin, angle);
    };


    my.importStateToStateMigrationFlows = function (keepSelectedState) {
        importStateToStateMigrationFlows(keepSelectedState);
    };

    my.importTotalCountyFlowData = function (stateFIPS) {
        importTotalCountyFlowData(stateFIPS);
    };

    /**
     * Filter the provided model according to the current filterSettings
     */
    my.filterBySettings = function (m, settings) {
        var filteredModel;
        //my.logFlows(model_master);
        filteredModel = new Flox.ModelFilter(m).filterBySettings(settings);

        return filteredModel;
    };

    /**
     * Filters, performs layout, and displays the model according to current settings.
     */
    my.updateMap = function () {

        mapComponent.clearAllMapFeatures();
        //开启toolTip
        mapComponent.enableTooltip();
        Flox.GUI.showLayoutProgressBar();

        // Assign xy coordinates to nodes.
        my.assignXYToNodes(model_master.getPoints());

        //model_master.setMaxFlowWidth();

        // This if statement is here for debug just to make it easier to turn
        // off webworkers in order to run performace tests.
        // TODO Eventually, it might be nice to have the ability to not use
        // webworkers if the browser is not compatible with them.
        // FIXME Something is broken when not using webworkers.
        // TODO throw up a "please wait" sign
        var filteredModel,
            largestFlowsModel = new Flox.Model(), i, oldFlows, newFlows, oldCPt,
            newCPt;
        filteredModel = my.filterBySettings(model_master, filterSettings);

        numberOfDisplayedFlows = filteredModel.getLargestFlows().length;
        currentFilteredModel = filteredModel;

        Flox.GUI.updateGUI();
        if (filterSettings.stateMode === false) {
            mapComponent.configureNecklaceMap(filteredModel);
        }
        largestFlowsModel.updateSettings(filteredModel.settings);
        largestFlowsModel.addFlows(filteredModel.getLargestFlows());

        layoutFlows(largestFlowsModel);

        oldFlows = filteredModel.getLargestFlows();
        newFlows = largestFlowsModel.getFlows();

        // change the locations of the control points in the filtered model.
        for (i = 0; i < newFlows.length; i += 1) {
            oldCPt = oldFlows[i].getCtrlPt();
            newCPt = newFlows[i].getCtrlPt();
            oldCPt.x = newCPt.x;
            oldCPt.y = newCPt.y;
        }

        Flox.newFlows = newFlows;
        var matrixGraphic = new MatrixGraphic();
        Flox.matrixGraphic = matrixGraphic;
        matrixGraphic.drawMatrixGraphic(newFlows);
        // 绘制弦图
        var chordDiagram = new ChordDiagram();
        Flox.chordDiagram = chordDiagram;
        chordDiagram.drawChordDiagram(newFlows);


        var gapminderGraphic = new GapminderGraphic();
        Flox.gapminderGraphic = gapminderGraphic;
        gapminderGraphic.drawGapminderGraphic(newFlows);

        //底图分级设色
        mapComponent.setChoroplethAndLegend(filteredModel);
        // TODO take down the "please wait" sign
        refreshMap(largestFlowsModel);
    };

    my.getFlowType = function () {
        return filterSettings.flowType;
    };

    my.setFlowType = function (newSetting) {
        var possibleSettings = ["net", "total", "incoming", "outgoing"];
        if (possibleSettings.indexOf(newSetting) < 0) {
            throw new Error("Not a possible setting for flowType: " + newSetting);
        }

        filterSettings.flowType = newSetting;

        if (newSetting === "total") {
            model_master.settings.drawArrows = false;
        } else {
            model_master.settings.drawArrows = true;
        }
    };

// state is false if no state is selected
    my.getSelectedState = function () {
        return filterSettings.selectedState;
    };

    my.setSelectedState = function (state) {
        var flowType;
        // if state is false, we gotta do some stuff.
        if (state) {
            filterSettings.selectedState = true;
            flowType = my.getFlowType();
            // if (flowType === "incoming" || flowType === "outgoing") {
            //     my.setFlowType("total");
            // }
        } else {
            filterSettings.selectedState = false;
        }
        filterSettings.selectedState = state;
        // Flox.GUI.updateFlowTypeRadioButtons();
    };

    my.getSelectedCounty = function () {
        return filterSettings.selectedCounty;
    };

    my.setSelectedCounty = function (county) {
        var flowType;
        if (county === false) {
            flowType = my.getFlowType();
            // if (flowType === "incoming" || flowType === "outgoing") {
            //     my.setFlowType("total");
            // }
        }
        filterSettings.selectedCounty = county;

        Flox.GUI.updateFlowTypeRadioButtons();

    };

    my.getSelectedFeatureName = function () {
        return filterSettings.selectedFeatureName;
    };

    my.setSelectedFeatureName = function (newSelectedFeatureName) {
        filterSettings.selectedFeatureName = newSelectedFeatureName;
    };

    my.isCountyMode = function () {
        return filterSettings.countyMode;
    };

    my.setCountyMode = function (boo) {
        filterSettings.countyMode = boo;
    };

    my.isStateMode = function () {
        return filterSettings.stateMode;
    };

    my.setStateMode = function (boo) {
        filterSettings.stateMode = boo;
    };

    my.selectState = function (stateFIPS) {
        mapComponent.selectState(stateFIPS);
    };

    my.isOuterStateFlows = function () {
        return filterSettings.outerStateFlows;
    };

    my.setOuterStateFlows = function (boo) {
        filterSettings.outerStateFlows = boo;
    };

    my.isInStateFlows = function () {
        return filterSettings.inStateFlows;
    };

    my.setInStateFlows = function (boo) {
        filterSettings.inStateFlows = boo;
    };

    my.initFlox = function () {

        // Flox.GUI.updateGUI();
        model_master = new Flox.Model();
        mapComponent = new Flox.MapComponent_d3();
        mapComponent.initMap();

        // filterSettings.stateMode = true;

        importStateToStateMigrationFlows();
    };

// This runs the startup animation. It's rather hacked in. Like other things.
    my.runInitialAnimation = function () {
        $("#loadingMessage").remove();
        $("#mouseBlocker").css("background", "none");
        mapComponent.zoomToFullExtent();
        setTimeout(function () {
            Flox.GUI.toggleLegendSlidingPanel();
            setTimeout(function () {
                Flox.GUI.toggleSlidingPanel();
                setTimeout(function () {
                    Flox.GUI.toggleOptionsSlidingPanel();
                    $("#mouseBlocker").css("pointer-events", "none");
                }, 50);
            }, 50);
        }, 750);
    };

    my.logFlows = function (model) {
        model.sortFlows();
        var flows = model.getAllFlows(),
            i, j, f;

        for (i = 0, j = flows.length; i < j; i += 1) {
            f = flows[i];
            console.log(f.getValue() + " : "
                + f.getStartPt().name + ", " + f.getStartPt().STUSPS + " to "
                + f.getEndPt().name + ", " + f.getEndPt().STUSPS);
        }
    };

    my.logModel_master = function () {
        model_master.sortFlows();
        var flows = model_master.getAllFlows(),
            i, j, f;
        for (i = 0, j = flows.length; i < j; i += 1) {
            f = flows[i];
            console.log(f.getValue() + " : "
                + f.getStartPt().name + ", " + f.getStartPt().STUSPS + " to "
                + f.getEndPt().name + ", " + f.getEndPt().STUSPS);
        }

    };

    my.getObstacles = function (model) {
        var layouter = new Flox.FlowLayouter(model);
        return layouter.getObstacles();
    };

    my.layoutFlows = function (m) {
        layoutFlows(m);
    };

    my.startTimer = function () {
        startTimeAll = performance.now();
    };

    my.endTimer = function () {
        endTimeAll = performance.now();
        console.log("TOTAL time in milliseconds: " + Math.round(endTimeAll - startTimeAll));
    };

    my.getNodeCoordinates = function () {
        var nodes = model_master.getPoints(),
            coords = [], i;

        for (i = 0; i < nodes.length; i += 1) {
            coords.push([nodes[i].x, nodes[i].y]);
        }
        return coords;
    };

    my.getPopulationDensityColor = function () {
        return mapComponent.getPopulationDensityColor();
    };

    my.enableTooltip = function () {
        mapComponent.enableTooltip();
    };

    my.disableTooltip = function () {
        mapComponent.disableTooltip();
    };

    my.getSelectedStateName = function () {
        if (filterSettings.selectedState === false) {
            throw new Error("No state is selected");
        }
        return filterSettings.selectedFeatureName;
    };

    my.zoomToCircle = function (c) {
        mapComponent.zoomToCircle(c);
    };

    my.zoomToRectangle = function (rect) {
        mapComponent.zoomToRectangle(rect);
    };

    my.getNumberOfDisplayedFlows = function () {
        return numberOfDisplayedFlows;
    };

    my.getCurrentFilteredModel = function () {
        return currentFilteredModel;
    };

    my.getMapScale = function () {
        return mapComponent.getMapScale();
    };
    return my;
}());

Flox.FlowImporter = FlowImporter;
Flox.Flow = Flow;
Flox.ModelFilter = ModelFilter;
Flox.FlowLayouter = FlowLayouter;
Flox.GeomUtils = GeomUtils;
Flox.ColorUtils = ColorUtils;
Flox.MapComponent_d3 = MapComponent_d3;
Flox.Model = Model;
window.Flox = Flox;
export  default Flox;
